package emu.decvices.cartridge.mappers;

import emu.decvices.bus.BusDevice;
import emu.decvices.cartridge.Cartridge;

import java.io.Serializable;

/**
 * 卡带中的地址映射器，实际上是在游戏卡中的一个硬件部分
 * <p>
 * {@link https://www.nesdev.org/wiki/Mapper NES WIKI}
 */
public abstract class Mapper implements Serializable {
    protected RomHeader romHeader;

    protected Cartridge cartridge;

    private MirrorDevice mirrorDevice;

    protected byte prgData[];
    protected byte chrData[];

    public Mapper(Cartridge cartridge) {
        this.romHeader = cartridge.getRomHeader();
        this.cartridge = cartridge;
        this.chrData = cartridge.getChrData();
        this.prgData = cartridge.getPrgData();
        mirrorDevice = new MirrorDevice(cartridge.getMirror());
        if(romHeader.getChrRomChunks()==0){
            chrData = new byte[0x2000];
        }
    }

    /*
    对PPU的VRAM映射,解决水平映射和垂直映射等
     */
    public BusDevice getPpuVRamMapper() {
        return mirrorDevice;
    }

    public abstract BusDevice getCpuDevice();

    public abstract BusDevice getPpuDevice();

    /**
     * 改变VRAM中nametable的镜像方式
     */
    private class MirrorDevice extends BusDevice{
        private boolean self = false;
        private Cartridge.Mirror mirror;

        public MirrorDevice(Cartridge.Mirror mirror) {
            this.mirror = mirror;
        }
        @Override
        public int fromDeviceRead(int address) {
            if(address >= 0x2000 & address < 0x3F00) {
                switch (mirror) {
                    case HORIZONTAL:
                        return doHorizontalRead(address);
                    case VERTICAL:
                        return doVerticalRead(address);
                }
            }
            return -1;
        }

        @Override
        public boolean toDeviceWrite(int address, int data) {
            if(address >= 0x2000 & address < 0x3F00) {
                switch (mirror) {
                    case HORIZONTAL:
                        return doHorizontalWrite(address, data);
                    case VERTICAL:
                        return doVerticalWrite(address, data);
                }
            }
            return false;
        }

        private boolean doHorizontalWrite(int address,int data){
            if (!self && address >= 0x2000 & address < 0x3F00) {
                if(address>=0x3000){
                    address&=0x27FF;
                }
                if (address >= 0x2400 && address < 0x2800) {
                    address = address - 0x400;
                } else if (address >= 0x2800 && address < 0x2C00) {
                    address = address - 0x400;
                }
                self = true;
                toBusWrite(address, data);
                self = false;
                return true;
            }
            return false;
        }
        private int doHorizontalRead(int address){
            if (!self && address >= 0x2000 & address < 0x3F00) {
                if(address>=0x3000){
                    address&=0x27FF;
                }
                if (address >= 0x2400 && address < 0x2800) {
                    address = address - 0x400;
                } else if (address >= 0x2800 && address < 0x2C00) {
                    address = address - 0x400;
                }
                self = true;
                int d = fromBusRead(address);
                self = false;
                return d;
            }
            return -1;
        }

        private boolean doVerticalWrite(int address, int data){
            if (address >= 2000 && address <= 0x3F00) {
                if (!self && address >= 0x2800) {
                    address = (address&0x27FF);
                    self = true;
                    toBusWrite(address, data);
                    self = false;
                    return true;
                }
            }
            return false;
        }
        private int doVerticalRead(int address){
            if (address >= 2000 && address <= 0x3F00) {
                if (!self && address >= 0x2800) {
                    address = (address&0x27FF);
                    self = true;
                    int d = fromBusRead(address);
                    self = false;
                    return d;
                }
            }
            return -1;
        }
    }

    public void setMirror(Cartridge.Mirror mirror){
        mirrorDevice.mirror = mirror;
    }

    //PPU扫描线
    public void onScanLine(){
    }

}
